package reentrant;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 重入锁, 与之前的 Mutex 相比, 支持同一个线程对资源重复加锁, 且支持公平选择
 * synchronized 支持隐式的重进入
 * ReentrantLock 支持再次调用 lock() 而不被阻塞
 * 
 * 非公平下, ReentrantLock 调用的是 nonfairTryAcquire() & tryRelease()
 * 其中获取同步状态可以叠加 释放锁会释放 n 次完全释放后才返回 true
 * 公平锁在获取时多了一个 hasQueuedPredecessors() 判断是否有前驱节点
 * 
 * 其次, 因为公平锁顺序推进, 切换次数多, 非公平重复获取锁概率较大, 切换次数少
 * 而线程切换也有成本, 因此非公平性能更好, 但性能不是万能的
 * 非公平会导致线程饥饿概率上升, 公平锁概率小, 容易满足饥饿请求
 * 
 * 测试公平与否的区别
 * 
 * @author insight
 * @since 2021/9/13
 */
public class FairAndUnFairTest {
    private static ReentrantLockImpl unfairLock = new ReentrantLockImpl(false);
    private static ReentrantLockImpl fairLock = new ReentrantLockImpl(true);

    public static void main(String[] args) {
        // 公平锁按顺序推进
        // testLock(fairLock);
        // 非公平出现了一个线程重复获取锁的情况
        testLock(unfairLock);
    }

    private static void testLock(ReentrantLockImpl fairLock) {
        int threadCnt = 5;
        for (int i = 0; i < threadCnt; i++) {
            Job job = new Job(fairLock);
            job.setName(String.valueOf(i + 1));
            job.start();
        }
    }

    static class Job extends Thread {
        private ReentrantLockImpl lock;

        public Job(ReentrantLockImpl lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            lock.lock();
            System.out.printf("Lock by [%s], Waiting by %s\n", Thread.currentThread().getName(), lock.getQueuedThreads());
            lock.unlock();
            lock.lock();
            System.out.printf("Lock by [%s], Waiting by %s\n", Thread.currentThread().getName(), lock.getQueuedThreads());
            lock.unlock();
        }
    }
    

    static class ReentrantLockImpl extends ReentrantLock {
        public ReentrantLockImpl(boolean fair) {
            super(fair);
        }

        @Override
        protected Collection<Thread> getQueuedThreads() {
            ArrayList<Thread> threads = new ArrayList<>(super.getQueuedThreads());
            Collections.reverse(threads);
            return threads;
        }
    }
    
}